# -*- coding: utf-8 -*-

"""
 (c) 2023 - Copyright CTyunOS Inc

 Authors:
   youyifeng <youyf2@chinatelecom.cn>

"""

import datetime
import json
import logging
from optparse import OptionParser

from cve_ease import activate_session, Scraper
from cve_ease.helper import one_instance_check, get_wanip, get_watcher
from cve_ease.models import CVE, SA, CVELOG, SALOG
from cve_ease.notifier import NotifierBase

logger = logging.getLogger('cve-ease')


def get_usage_str(usage):
    return usage + "\n(Specify the --help global option for a list of other help options)"


def compare(last_dict_list_str, newer_dict_list_str, sorted_by):
    """
    compare string
    :param last_dict_list_str:
    :param newer_dict_list_str:
    :param sorted_by:
    :return:
    """
    diff = {
        "modify": [],
        "add": [],
        "delete": [],
        "data": [],
    }
    newer_dict_list = json.loads(newer_dict_list_str)
    last_dict_list = json.loads(last_dict_list_str)
    map_newer_dict_list = {}
    map_last_dict_list = {}

    for rd in newer_dict_list:
        map_newer_dict_list[rd[sorted_by]] = rd
    for rd in last_dict_list:
        map_last_dict_list[rd[sorted_by]] = rd

    for cveId in map_newer_dict_list.keys():
        if cveId not in map_last_dict_list:
            diff['add'].append(map_newer_dict_list[cveId])
            continue
        for key in map_newer_dict_list[cveId].keys():
            if map_newer_dict_list[cveId][key] != map_last_dict_list[cveId][key]:
                diff['modify'].append(map_newer_dict_list[cveId])
                break
    for cveId in map_last_dict_list.keys():
        if cveId not in map_newer_dict_list:
            diff['delete'].append(map_last_dict_list[cveId])
    diff['data'] = newer_dict_list
    return diff


def update_notify(gconfig, info_diff: dict, info_type: str):
    """
    use for update notify
    :param gconfig:
    :param info_diff: diff dict, contain modify/add/delete/data list
    :param info_type:
    :return:
    """
    for subclass in NotifierBase.__subclasses__():
        notifier_instance = subclass(gconfig)
        notifier_instance.do_update_notify(info_diff, info_type)


def status_notify(gconfig, info_list: list, info_type: str):
    """
    use for status notify
    :param gconfig:
    :param info_list: data list
    :param info_type:
    :return:
    """
    for subclass in NotifierBase.__subclasses__():
        notifier_instance = subclass(gconfig)
        notifier_instance.do_status_notify(info_list, info_type)


def handle_daemon(gconfig, db_session, args):
    """[basic] Run as daemon without interactive"""

    # one instance
    lock = one_instance_check(gconfig.LOCK_FILE_PATH)
    if lock:
        raise Exception(f"Another ease already running")
    usage = "usage: %prog daemon <options>"
    parser = OptionParser(usage=get_usage_str(usage))

    parser.add_option('-v', '--verbose', dest='verbose', action='store_true', default=False,
                      help='show verbose output')

    (options, args) = parser.parse_args(args)

    # setup wanip
    setattr(gconfig, 'WANIP', get_wanip())
    setattr(gconfig, 'WATCHER', get_watcher(gconfig))

    session = activate_session(db_session, gconfig)
    logger.debug(" * active db sql session")

    scraper = Scraper()
    total = session.query(CVE).count()
    # info_type = "cve"
    response = scraper.scrapyCVE(total)
    if "code" in response and response["code"] == 0:
        logger.debug(" * scrapy CVE from OpenEuler done")
        totalCount = response["result"]["totalCount"]
        logger.debug(" * total record num: %d" % totalCount)
        cveDatabaseList = response["result"]["cveDatabaseList"]

        count = session.query(CVELOG).count()
        if count == 0:
            # recreate CVE db
            CVE.__table__.drop(db_session.get_engine())
            CVE.__table__.create(db_session.get_engine())
            session.bulk_insert_mappings(CVE, cveDatabaseList)
            session.commit()
            # add cvelog
            log = CVELOG()
            log.code = response["code"]
            log.msg = response["msg"]
            log.totalCount = response["result"]["totalCount"]
            log.cveDatabaseList = json.dumps(response["result"]["cveDatabaseList"])
            session.add(log)
            session.commit()
        else:
            # find last one
            last_cve_record = session.query(CVELOG).order_by(CVELOG.id.desc()).first()

            if last_cve_record.cveDatabaseList != json.dumps(cveDatabaseList):
                logger.debug(" * cve updated!")
                # recreate CVE db
                #CVE.__table__.drop(db_session.get_engine())
                #CVE.__table__.create(db_session.get_engine())
                cveDatabaseList = list({frozenset(item.items()): item for item in cveDatabaseList }.values())
                session.bulk_insert_mappings(CVE, [ cveData  for cveData in cveDatabaseList 
                                               if session.query(CVE).filter(CVE.id == cveData['id']).count()==0])
                session.commit()

                # compare old/new record
                diff = compare(last_cve_record.cveDatabaseList, json.dumps(cveDatabaseList), sorted_by='cveId')
                # notify alert update info
                if len(diff['add']) > 0 or len(diff['modify']) > 0 or len(diff['delete']) > 0:
                    # update notify
                    update_notify(gconfig, diff, info_type="cve")

                # add cve updated log
                log = CVELOG()
                log.code = response["code"]
                log.msg = response["msg"]
                log.totalCount = response["result"]["totalCount"]
                log.cveDatabaseList = json.dumps(response["result"]["cveDatabaseList"])
                log.add = len(diff["add"])
                log.delete = len(diff["delete"])
                log.modify = len(diff["modify"])

                session.add(log)
                session.commit()
            else:
                logger.debug(" * cve without updated!")
            # clean old record
            expiration_days = int(gconfig.EXPIRATION_DAYS)
            limit = datetime.datetime.now() - datetime.timedelta(days=expiration_days)
            session.query(CVELOG).filter(CVELOG.created_at <= limit).delete()
            session.commit()

        # status notify
        status_notify(gconfig, cveDatabaseList, info_type="cve")
    else:
        logger.error("cve scrapy response error! no usefull info found!")

    # info_type = "sa"
    response = scraper.scrapySA()
    if "code" in response and response["code"] == 0:
        logger.debug(" * scrapy SA from OpenEuler done")
        totalCount = response["result"]["totalCount"]
        logger.debug(" * total record num: %d" % totalCount)
        securityNoticeList = response["result"]["securityNoticeList"]

        count = session.query(SALOG).count()
        if count == 0:
            # recreate sa db
            SA.__table__.drop(db_session.get_engine())
            SA.__table__.create(db_session.get_engine())
            session.bulk_insert_mappings(SA, securityNoticeList)
            session.commit()
            # add salog
            log = SALOG()
            log.code = response["code"]
            log.msg = response["msg"]
            log.totalCount = response["result"]["totalCount"]
            log.securityNoticeList = json.dumps(response["result"]["securityNoticeList"])
            session.add(log)
            session.commit()
        else:
            # find last one
            last_sa_record = session.query(SALOG).order_by(SALOG.id.desc()).first()

            # 可使用smash子命令，强行修改日志，测试 更新播报
            if last_sa_record.securityNoticeList != json.dumps(securityNoticeList):
                logger.debug(" * sa updated!")
                # recreate SA db
                SA.__table__.drop(db_session.get_engine())
                SA.__table__.create(db_session.get_engine())
                session.bulk_insert_mappings(SA, securityNoticeList)
                session.commit()

                # compare old/new record
                diff = compare(last_sa_record.securityNoticeList, json.dumps(securityNoticeList),
                               sorted_by='securityNoticeNo')

                # notify alert update info
                if len(diff['add']) > 0 or len(diff['modify']) > 0 or len(diff['delete']) > 0:
                    # updfate notify
                    update_notify(gconfig, diff, info_type="sa")

                # add sa updated log
                log = SALOG()
                log.code = response["code"]
                log.msg = response["msg"]
                log.totalCount = response["result"]["totalCount"]
                log.securityNoticeList = json.dumps(response["result"]["securityNoticeList"])
                log.add = len(diff["add"])
                log.delete = len(diff["delete"])
                log.modify = len(diff["modify"])

                session.add(log)
                session.commit()
            else:
                logger.debug(" * sa without updated!")
            # clean old record
            expiration_days = int(gconfig.EXPIRATION_DAYS)
            limit = datetime.datetime.now() - datetime.timedelta(days=expiration_days)
            session.query(SALOG).filter(SALOG.created_at <= limit).delete()
            session.commit()

        # status notify
        status_notify(gconfig, securityNoticeList, info_type="sa")
    else:
        logger.error("sa scrapy response error! no usefull info found!")
